#ifndef TASK_TURBO
#define TASK_TURBO


#include <string>
#include <map>
#include <iostream>
#include "functions.h"
#include "patch.h"
#include "grid.h"
#include "model.h"
#include "method.h"
#include "parallel.h"

//Class that perform configuring actions
class Task {
private:
	Model* ChooseModel(std::string name);	//Avalible models
	Method* ChooseMethod(std::string name);;	//Availible methods	
	std::string gridType; //Grid type
	std::string solutionFileName;	
	//Initial section
	void Init();	//Fill lists of availible methods, models	
public:	
	Model* CurrentModel;	//Chosen model
	Method* CurrentMethod;	//Chosen method
	Grid CurrentGrid;	//Chosen grid	
	Parallel Par;	//Parallel implementation
	param_list ConfigParameters;	//Configuration parameters
	//Initialize task class
	Task(int *argc, char **argv[]) {
		//Init parallel part
		Par.Init(argc, argv);
		Init();		
	};
	//Load configuration info
	void LoadTask(const char *fname);
	void OutputResult();

	//Start task
	void Start()
	{
		CurrentMethod->Run();
		OutputResult();
		if (Par.IsMaster()) printf("Finished!\n");
		return;
	};
};

//Intialize list of avalible options
void Task::Init()
{	
	return;
};

void Task::OutputResult()
{
	#ifndef LOCAL
	Par.Barrier();
	Grid sg = CurrentModel->grid;
	int varsize = CurrentModel->nv+1;
	double *rbuf = new double[varsize * CurrentGrid.cells.size()];
	double *sbuf = new double[varsize * sg.cells.size()];
	for (int i = 0; i<sg.cells.size(); i++) {
		sbuf[i*varsize] = sg.cells[i].GlobalIndex;
		for (int j = 1; j<=CurrentModel->nv; j++) sbuf[i*varsize + j] = CurrentModel->values[i][j-1];
	};
	Par.Gather(CurrentModel->nv+1, rbuf, sbuf);
	std::vector<int> decomposition(Par.pnodes);
	for (int i = 0; i<Par.pnodes.size(); i++) Par.pnodes[i] = 0;
	if (Par.IsMaster()) {
		Model M(CurrentGrid, Par, ConfigParameters);
		Par.pnodes = decomposition;
		M.LoadConfig();
		for (int i = 0; i<CurrentGrid.cells.size(); i++) {		
			var_vector val(M.nv);
			for (int j = 1; j<=CurrentModel->nv; j++) val[j-1] = rbuf[i*varsize + j];
			M.values[(int)CurrentGrid.cell_index[rbuf[i*varsize]]] = val;
		};
		if (gridType == "2D") {
			M.ToTecPlotSol2DSq(solutionFileName.c_str());
		};
		if (gridType == "2DTriangle") {
			M.ToTecPlotSol2DTr(solutionFileName.c_str());
		};
		if (gridType == "2DGen") {
			M.ToTecPlotSol2DSq(solutionFileName.c_str());
		};
		if (gridType == "2DCylGen") {
			M.ToTecPlotSol2DSq(solutionFileName.c_str());
		};
		if (gridType == "3D") {
			M.ToTecPlot(solutionFileName.c_str());
		};
		if (gridType == "3DCyl") {
			M.ToTecPlotHex(solutionFileName.c_str());
			M.ToTecPlot3Dsurface("3D_distribution.dat", 3);
		};
	};
	#else
	if (gridType == "2D") {
		CurrentModel->ToTecPlotSol2DSq(solutionFileName.c_str());
	};
	if (gridType == "2DTriangle") {
		CurrentModel->ToTecPlotSol2DTr(solutionFileName.c_str());
	};
	if (gridType == "2DGen") {
		CurrentModel->ToTecPlotSol2DSq(solutionFileName.c_str());
	};
	if (gridType == "2DCylGen") {
		CurrentModel->ToTecPlotSol2DSq(solutionFileName.c_str());
	};
	if (gridType == "3D") {
		CurrentModel->ToTecPlot(solutionFileName.c_str());
	};		
	if (gridType == "3DCyl") {
		CurrentModel->ToTecPlotHex(solutionFileName.c_str());
		CurrentModel->ToTecPlot3Dsurface("3D_distribution.dat", 3);
	};

	#endif
	return;
};

//Load task using config
void Task::LoadTask(const char *fname)
{		
	ConfigParameters = ParseConfig(fname);
	std::istringstream ss;	
	//Get all necessary parameters from list
	gridType = GetParam(ConfigParameters, "GridType")[0];
	solutionFileName = GetParam(ConfigParameters, "SolutionFileName")[0];	
	std::string modelName = GetParam(ConfigParameters, "Model")[0];	
	std::string methodName = GetParam(ConfigParameters, "Method")[0];	
	//Load grid considering grid type
	if (gridType == "2D") {			//TO DO 2D -> 2DSqure	
		std::string gridFileName = GetParam(ConfigParameters, "GridFile")[0];
		ss.str(gridFileName);
		std::string nodes, cells, faces;
		ss>>nodes>>cells>>faces;
		int N_nodes = 4;
		CurrentGrid.GridRead(nodes.c_str(), cells.c_str(), faces.c_str(), N_nodes);
	};
	if (gridType == "2DTriangle") {
		std::string gridFileName = GetParam(ConfigParameters, "GridFile")[0];
		ss.str(gridFileName);
		std::string nodes, cells, faces;
		ss>>nodes>>cells>>faces;
		int N_nodes = 3;
		CurrentGrid.GridRead(nodes.c_str(), cells.c_str(), faces.c_str(), N_nodes);
	};
	if (gridType == "3D") {
		std::string gridFileName = GetParam(ConfigParameters, "GridFile")[0];
		CurrentGrid.GridLoad(gridFileName.c_str());
	};	
	if (gridType == "3DCyl") {
		std::string gridFileName = GetParam(ConfigParameters, "GridFile")[0];
		CurrentGrid.GridLoadCyl(gridFileName.c_str());
	};
	if (gridType == "2DGen") {
		std::string gridParams = GetParam(ConfigParameters, "GridParams")[0];
		ss.str(gridParams);
		int nx, ny;
		double x_size, y_size, x_q, y_q;
		ss>>nx>>ny>>x_size>>y_size>>x_q>>y_q;
		CurrentGrid = GenGrid(nx, ny, x_size, y_size, x_q, y_q);
	};
	if (gridType == "2DCylGen") {
		//TO DO prepare grid
		std::string gridParams = GetParam(ConfigParameters, "GridParams")[0];
		ss.str(gridParams);
		int nx, ny;
		double x_size, y_size, x_q, y_q;	
		ss>>nx>>ny>>x_size>>y_size>>x_q>>y_q;
		CurrentGrid = GenGrid(nx, ny, x_size, y_size, x_q, y_q);	
		double h_size, c_size;
		std::string tmp = GetParam(ConfigParameters, "ColdSize")[0];
		ss.clear(); ss.str(tmp); ss >> c_size;
		tmp = GetParam(ConfigParameters, "HotSize")[0];
		ss.clear(); ss.str(tmp); ss >> h_size;
		for (int i = 0; i<CurrentGrid.faces.size(); i++) {
			Vector p = CurrentGrid.faces[i].FaceCenter;
			if (p.y == y_size) CurrentGrid.faces[i].BCMarker = 1;
			if (p.y == 0.0) CurrentGrid.faces[i].BCMarker = 1;
			if (p.x == x_size) CurrentGrid.faces[i].BCMarker = 1;
			if (p.x == 0.0) CurrentGrid.faces[i].BCMarker = 1;
			if ((p.x == 0.0) && (p.y<=c_size)) CurrentGrid.faces[i].BCMarker = 2;
			if ((p.x == x_size) && ((y_size - p.y) <= h_size) ) CurrentGrid.faces[i].BCMarker = 3;				
		};
		//Lower grid size by a factor	
		//CurrentGrid.Deform(0.1);
		bool inlet;
		double inlet_h;
		double inlet_w;
		double inlet_shift;
		tmp = GetParam(ConfigParameters, "Inlet")[0];
		ss.clear(); ss.str(tmp); ss >> inlet;
		tmp = GetParam(ConfigParameters, "InletShift")[0];
		ss.clear(); ss.str(tmp); ss >> inlet_shift;
		tmp = GetParam(ConfigParameters, "InletHeight")[0];
		ss.clear(); ss.str(tmp); ss >> inlet_h;
		tmp = GetParam(ConfigParameters, "InletWidth")[0];
		ss.clear(); ss.str(tmp); ss >> inlet_w;
		CurrentGrid.ConvertToCylinder(inlet, inlet_shift, inlet_w, inlet_h);
	};
	//Report if grid were loaded successfully
	if (Par.IsMaster()) {
		printf("Mesh were succesfully loaded!\n");
		printf("nodes %ld, cells %ld, faces %ld\n", CurrentGrid.nodes.size(), CurrentGrid.cells.size(), CurrentGrid.faces.size());
	};
	//Perform domain decomposition	
	int edgecut = 0;
	if (gridType == "3DCyl") {
		edgecut = Par.CartesianDecomposition(CurrentGrid);
	} else {
		edgecut = Par.Decomposition(CurrentGrid);
	};
	if (Par.IsMaster()) {
		//CurrentGrid.ToTecPlotHex("grid.dat");		
	};
	Par.Barrier();	

	if (Par.IsMaster()) printf("After decomposition edgecut = %ld\n", edgecut);
	//Choose model and load model configuration
	if ((CurrentModel = ChooseModel(modelName)) == NULL) {
		std::cout<<"That model isnt supported.\n";
		exit(0);
	};	
	
	//Clear grid info from memory on non-master nodes
	if (!Par.IsMaster()) {
		CurrentGrid.Clear();
	};
	//Configure particular model
	CurrentModel->LoadConfig();
	CurrentModel->InitParallel();
	//Choose method
	if ((CurrentMethod = ChooseMethod(methodName)) == NULL) {
		std::cout<<"That method isnt supported.\n";
		exit(0);
	};
	//Configure chosen method
	CurrentMethod->LoadConfig();
	Par.Barrier();
};

//Here we define what models are supproted
Model* Task::ChooseModel(std::string name)
{	
	if (name == "GasModel") {				
		return new Model(CurrentGrid, Par, ConfigParameters);
	};		
	return NULL;
};

//Here we define all methods availible
Method* Task::ChooseMethod(std::string name)
{	
	if (name == "FiniteVolume") {		
		return new Method(*CurrentModel);
	};		
	return NULL;
};

#endif
